import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
//import java.util.Map;
import java.util.Queue;
import rand.RNG;

/*
Main simulation file to launch the queue simulation at the airport 
Project description: Recreate the flow of passengers from taxi-out from their departing airports, to their flight to SYD , to the 
taxi of the flights into the gates, the travel of the passengers to Immigration and their processing of the passengers
Goal: Discrete Event Driven Simulation of the flow of passengers inside the airport to recreate staffing levels at immi
		Change the service rate until you can match the time at immi of the simulation to the actual time at immigration per gate
Simulation Processes:
	1. Simulate flights arrival on runways(optional)
	2. Simulate flights arrival at gates(optional)
	3. Simulate walk times of different passengers from gates to immi
	4. Assume a service rate at immigration desks, and simulate the immigration services until the Time spent at immigration get matched
	5. Repeat from step 3 with a new service rate at immigration

Simulation Components:
	1. Data extractor class for time2Immi and timeAtImmi classes depending on gate
	2. Class that uses the scheduled flights to determine the timing of the arrivals on a given day
	3. Class that gets the population on each flight based on immigration data
	4. Function that simulates the different passengers arrivals at immi, then simulate the queue length, to approximate 
		the time the passengers will take in a queue.
	5. Function that compares the collected simulation statistics to the immigration statistics to validate the service rate	
	6. Data extractor: Print the data, and extract relevant data to a file
*/

/**
 * 
 *@author: Harold Nikoue 
 */
public class Main{
	private static BinaryHeap<Flight> arrivalQueue;
	private static Queue<eventTime> arrivalTimes;//times of arrival of passengers at immigration from different gates
	private static double rateOfPAX[] = new double[24];//rate of passengers per hour being processed at Immi
	private static int nbPAX[]= new int[24]; //nb of passengers per hour being processed at Immi
	private static int totalPAX = 0;
	private static double totalTime =0.0;
	private static  Hashtable<String,TimeStat> time2Immi;
	private static double assumedServiceRate;//assumed process rate of passengers at immi in pax per hour
	private static double timePerHour = 0.0;
	static int hourIndex=0;
	private static int NB_OPEN_DESKS = 6;
//	private static Hashtable<String,gateArrivalInfo> gateInfo = new Hashtable<String,gateArrivalInfo>();
	private static LocalArrTime dayOfInterest;
	private static ArrayList<Flight> flightsToday;//flights on that day of interest
	private static ArrayList<Flight> actualFlightInfo = new ArrayList<Flight>();
	private static Hashtable<Integer,Flight> simulationATimes = new Hashtable<Integer,Flight>();
	private static double actualHour[] = new double[24];
	public static boolean flightsUsedIndex[];//index of all the flights used in the simulation

	/**
	 * Main function to run the simulation
	 * Extract the flights
	 * @param args
	 */
	public static void main(String[] args){
		/*
		 * Initialization
		 */
		double dRMS = 1e100,RMS2,RMS1;
		assumedServiceRate = 1000;
		String dayOfInterestStr = "1/9/2012";
		dayOfInterest = new LocalArrTime(dayOfInterestStr);//the day we pick, it should be available across all files
		PassengersGateTime arrivalFlights = new PassengersGateTime(dayOfInterest);
		flightsToday = arrivalFlights.flightHistory();
		flightsUsedIndex = new boolean[flightsToday.size()];
		for(int i =0;i<flightsToday.size();i++){
			flightsUsedIndex[i] = false;
		}
		arrivalQueue = new BinaryHeap<Flight>(Flight.class);
		
		/*
		 * Program
		 */
		extractFlightData(); //flights arrivals information
		extracTimeData(); //time2Immi data and timeAtImmi data
		processGates();//process all passengers for that day and assign them an arrival time at Immi
		serveQueues();//Once the tree is full serve all of them using an assumed service rate
		RMS1 = compareStatistics();
		while((Math.abs(dRMS)>1e4 || RMS1>1e7) && assumedServiceRate>0){
			flightsToday = arrivalFlights.flightHistory();
			flightsUsedIndex = new boolean[flightsToday.size()];
			for(int i =0;i<flightsToday.size();i++){
				flightsUsedIndex[i] = false;
			}
			arrivalQueue = new BinaryHeap<Flight>(Flight.class);
			
			/*
			 * Program
			 */
			extractFlightData(); //flights arrivals information
			extracTimeData(); //time2Immi data and timeAtImmi data
			processGates();//process all passengers for that day and assign them an arrival time at Immi
			assumedServiceRate -= 5;
			RMS2 = RMS1;
			serveQueues();//Once the tree is full serve all of them using an assumed service rate
			RMS1 = compareStatistics();
			dRMS = RMS2-RMS1;
			
		}
//		getOutputAnalysis();//obtain the resulting values
		printResults();//print results to .csv
	}
	/**
	 * Extract Flight information from a FIDS file
	 * contains flight id, block time, touchdown time,...
	 * @author hnikoue
	 */
	private static void extractFlightData(){
		 ReadFlight fl = new ReadFlight(dayOfInterest);
		 arrivalQueue = fl.getFlight();
		
	}
	/**
	 * Get the time at immi and to immi from device Id data
	 * use an HashTable for the time value per gate  so we have a HASHTABLE<String,Integer>
	 * We can get the walk-time or time at immi statistics based on a selected gate
	 */
	private static void extracTimeData() {
		//
		ReadT2Immi toImmi_dev = new ReadT2Immi();
		time2Immi = toImmi_dev.getMap();
//		ReadTAtImmi atImmi_dev = new ReadTAtImmi();
//		timeAtImmi = atImmi_dev.getMap();
		
	}
	

	
	

	/**
	 * Assign a walk time to each passenger per flight
	 */
	private static void processGates() {
		//ATTN meanPaxPerFlight are assumed numbers to define the distribution of passengers at the airport, they are not representative
		Flight flightInfo; //complete flight info
		long nbPAX;//number of passengers for this particular flight
		double walkTime;//walk time instance
		int c = 0;
		Flight currentFL;
		LocalArrTime timeNextEvent;
		TimeStat walkStat;
		eventTime arrEvent;//arrival event
		String currentGate;
		
		arrivalTimes = new BinaryHeap<eventTime>(eventTime.class);
		System.out.println("ProcessGates");
		while(!arrivalQueue.isEmpty()){

			
			/*
			 *The following block represents flight information (aggregation of passengers) 
			 */
			currentFL = arrivalQueue.poll();
			flightInfo = matchFlight(currentFL);//use matchflight to get the actual number of passengers
			actualFlightInfo.add(flightInfo);
			//get the walk times distribution for this passenger depending on the gate
			currentGate = currentFL.getGate();
			nbPAX = flightInfo.getNbOfPassengers();
			if((walkStat = time2Immi.get(currentGate))==null){
				System.out.println("Skip " +c+" at "+currentGate);
				continue;
			}
			
			LocalArrTime flightArrivalTime = currentFL.getTime();
			/*
			 * calculate the arrival times of all passengers at immi from our probability distribution
			 */
			for(int i=0;i<nbPAX;i++){
				timeNextEvent = new LocalArrTime(flightArrivalTime);// get the time of arrival of the flight
				walkTime = RNG.getLogNormal(walkStat.getMu(), walkStat.getSigma());
				while(walkTime>walkStat.getUpperSupport()){
					walkTime = RNG.getLogNormal(walkStat.getMu(), walkStat.getSigma());
				}
				timeNextEvent.addTime(walkTime);
				arrEvent = new eventTime(currentGate,timeNextEvent,currentFL.getFlightID());
				arrEvent.setTagID(currentFL.getTagID());
				arrivalTimes.add(arrEvent);
			}
			c++;
		}
		
		
	}
	/**
	 * match currentFL with an entry in flightsToday[ArrayList<Flight>] based on the gate and the flightID, and time difference
	 * the gate and the flightID must match
	 * flightToday are the flights extracted from the count_rate flight information
	 * @param currentFL from FIDS or arrivalQueue
	 * @return
	 */
	private static Flight matchFlight(Flight currentFL) {
		Flight testFlight,returnFL;
		returnFL = new Flight(currentFL.getFlightID(),currentFL.getGate(),currentFL.getFirstTimeAtImmi());
		boolean wasFound = false; //was this flight found in our dictionary of immigration information
//		int meanNbOfPassengers = 150;
//		int sigmaPAX = 25;//standard deviation
		
		for(int i = 0;i<flightsToday.size() && !wasFound;i++){
			testFlight = flightsToday.get(i);
			if(testFlight.getFlightID().equals(currentFL.getFlightID())){//flightIDs match
				if(testFlight.getGate().equals(currentFL.getGate())){//the gates match
					if(testFlight.getTime().isFromSameFlight(currentFL.getTime())){//The two flights occur less than 3 hours apart
						returnFL = testFlight;
						flightsUsedIndex[i] = true;
						wasFound = true;
					}
				}
			}
		}
		
		if(returnFL.getNbOfPassengers() ==0){
//			returnFL.setNbOfPassengers((int)RNG.getNormal(meanNbOfPassengers, sigmaPAX));
			returnFL.setNbOfPassengers(0);//by setting the number of passengers to zero I can make sure that these flights won't be part of the simulation
		}
		return returnFL;
	}


	/**
	 * Serve the immigration using an assumed service rate for all immigration desks
	 * The assumed service rate should actually be desk dependent
	 * Assume a multi-server queue
	 * in addition to the service rate it would be useful to add a buffer for passenger reaction time
	 */
	private static void serveQueues() {
		DeskTime clockTimes = new DeskTime(NB_OPEN_DESKS);//next availability times for a given desk
		LocalArrTime arrivalETime,previousTime;
		LocalArrTime simulationTime = null;//lowest clocktimes across all open desks
//		gateArrivalInfo tempGateInfo;
		eventTime arrivalEvent;
		boolean isStarted = false; //did the simulation start
		double serviceTime;
		int currentNode;
		int idx; //index of the clockTime with teh lowest time in the arrayList(DeskTime)
		Flight currentPAX=null,passengerGroup=null;
		Integer insertionHashStr;
		/*
		 *The way it is going to work
		 *For each event gets a service Time from the assumed service rate given by the user
		 *use the service time to define the time it takes the user to be processed by the immi
		 *then check with the next event to see if it can be processed
		 *If it cannot be processed: advances the clock time to the end of the current event, then process the event  
		 */
		System.out.println("serveQueues");
		while(!arrivalTimes.isEmpty()&& (totalTime<3600*24)){
			arrivalEvent = arrivalTimes.poll();//arrival event
			//aggregate information into gates
//			if(gateInfo.get(arrivalEvent.gate)==null){//This gate has not been seen yet
//				tempGateInfo = new gateArrivalInfo(arrivalEvent.time);
//				gateInfo.put(arrivalEvent.gate, tempGateInfo);
//			}
			clockTimes.find1stAvailableDesk();//poll the system to find an available desk
			//return the index of the first open desk, if less than NB_OPEN_DESKS are open
			//return the first available desk in time otherwise
			serviceTime = RNG.getExponential(assumedServiceRate/3600);
			arrivalETime = new LocalArrTime(arrivalEvent.getTime());//get the LocalArrTime object from the arrival event
			if(clockTimes.get1stAvailableTime()==null){//there is still at least one unoccupied desk
				if(!isStarted){//the simulation has not started 
					isStarted = true;
					simulationTime = arrivalETime;
				}
				currentNode = clockTimes.add(arrivalETime);//just add a node to the list as we haven't reached capacity
				clockTimes.getTimeAtIndex(currentNode).addTime(serviceTime);//add time to this clockTime
				continue;
			}else{
				simulationTime = new LocalArrTime(clockTimes.get1stAvailableTime());
				idx = clockTimes.indexOf1stAvailableDesk();//which desk is open
//				endOfEventTime.addTime(serviceTime);
				if(arrivalETime.compareTo(simulationTime)<=0){//Is the event scheduled earlier than any availability
					clockTimes.getTimeAtIndex(idx).addTime(serviceTime);//process it after the first availability
				}else{//the event is scheduled after the first gate becomes available
					/*
					 * Need to wait till the event time to process it
					 * It implies that I will look through the different desks availability times
					 */		
					currentNode = clockTimes.setTime(arrivalETime, idx);//move this simulation time to that index and remove lowest time
					clockTimes.getTimeAtIndex(currentNode).addTime(serviceTime);//add time to this clockTime
				}
			}
			
			//use arrivalEvent to obtain the gate of origin and the flight of this passenger
			//Record the processing of this passenger
			
			currentPAX = new Flight(arrivalEvent.getFlight(),arrivalEvent.getGate(),simulationTime);
			insertionHashStr = arrivalEvent.getTagID();
			if((passengerGroup = simulationATimes.get(insertionHashStr)) != null){//it is not null
				passengerGroup.updateTimeAtImmi(currentPAX.getTime());
				passengerGroup.setNbOfPassengers(passengerGroup.getNbOfPassengers()+1);
				
			}else{//create a new event
				passengerGroup = currentPAX;
				passengerGroup.updateTimeAtImmi(currentPAX.getFirstTimeAtImmi());
				passengerGroup.setNbOfPassengers(1);
				simulationATimes.put(insertionHashStr, passengerGroup);
			}
			
			//update the clocktimes and the simulation time
			clockTimes.find1stAvailableDesk();
			idx = clockTimes.indexOf1stAvailableDesk();
			previousTime = simulationTime;
			simulationTime = clockTimes.get1stAvailableTime();
			totalTime = totalTime +simulationTime.timeDiff(previousTime);
			timePerHour = timePerHour + simulationTime.timeDiff(previousTime);
			totalPAX++;
			
			if(timePerHour>= 3600){
				rateOfPAX[hourIndex] = nbPAX[hourIndex];
				actualHour[hourIndex] = simulationTime.getHour();
				hourIndex++;
				timePerHour = 0.0;
			}
			if (hourIndex<24)//don't go beyond the array bounds
				nbPAX[hourIndex] = nbPAX[hourIndex]+1;
		}
		
	}
	@SuppressWarnings("unused")
	private static LocalArrTime getTimeAfterSvce(LocalArrTime clockTime) {
		double serviceTime = 1/assumedServiceRate;
		if(clockTime !=null){
			clockTime.addTime(serviceTime);
		}
		return clockTime;
	}

//	private static void getOutputAnalysis() {
//		
//		
//	}
	/**
	 * Validate the results of the simulation with info from actualFlightInfo
	 * The number of flights from FIDS and from count_rate may or may not be the same
	 */
	private static double compareStatistics() {
		//Validate with info from immigration stored in DIMIA->PassengerGateTime->flightsToday
		int i = 0;
		double RMS = 0,sumAbsError = 0,meanAbsError,error;
		Flight actualFlight = null, simulatedFlight;
		Enumeration<Flight> keys = simulationATimes.elements();//elements of the hashTable in 
		while(keys.hasMoreElements()){
			
			simulatedFlight = keys.nextElement();
			actualFlight = matchFlight(simulatedFlight);
			if(actualFlight.getTimeSpentAtImmi() != 0){
				error = actualFlight.getTimeSpentAtImmi()-simulatedFlight.getTimeSpentAtImmi();
				RMS += (error)*(error);
				sumAbsError +=  (error);
			}
			i++;
		}
		
		meanAbsError = sumAbsError/i;
		System.out.println("\n");//make some room
		System.out.println("\n"+"For "+ i+ " flights,:");
		System.out.println("\t The root mean square error was: \t"+RMS);
		System.out.println("\t The average absolute error was: \t"+meanAbsError);
		return RMS;
	}

	private static void printResults() {
		System.out.println("In "+totalTime+" seconds "+totalPAX+" passengers were processed");
		System.out.println("There were :");
		
		for(int j=0;j<24;j++){
			System.out.println("\t"+nbPAX[j]+" at "+actualHour[j]+"h");
		}
		System.out.println("For an average of "+ totalPAX/(24)+" passengers per hour");
		
		//iterate through the hashtable
		Enumeration<Flight> keys = simulationATimes.elements();
		System.out.println("\n"+"FlightID \t gate \t number of PAX \t Arrival_time PAX1 \t time spent at Immi");
		while(keys.hasMoreElements()){
			System.out.println(keys.nextElement());
		}
		
		System.out.println("\n"+"Here are the flights that were not accounted for:");
		for(int j = 0; j<flightsToday.size();j++){
			if(!flightsUsedIndex[j]){
				System.out.println(flightsToday.get(j));//print the flight in question
			}
		}
		
	}
}
